package ltd.dujiabao.capability.adapter.properties;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;

import lombok.extern.slf4j.Slf4j;

/**
 * @author : DuJiabao
 * @Project : ddd-capability
 * @Time : 2024/4/15 22:04
 * @Desc :
 */
@Slf4j
@Component
@ConditionalOnProperty(name = "apollo.bootstrap.enabled", havingValue = "true", matchIfMissing = false)
public class PropertiesLiveRefresher {
    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private ApplicationEventPublisher publisher;

    @Autowired
    private ConfigurationPropertiesRebinder propertiesRebinder;

    @Value("#{'${apollo.bootstrap.namespaces:}'.split(',')}")
    private List<String> namespaces;

    @PostConstruct
    public void init() {
        if (CollectionUtils.isEmpty(namespaces)) {
            log.warn("namespaces is empty, could not live update properties.");
        }
        // 注册监听器
        for (String namespace : namespaces) {
            ConfigService.getConfig(namespace).addChangeListener(new BaseConfigChangedListener());
        }
    }

    private class BaseConfigChangedListener implements ConfigChangeListener {
        @Override
        public void onChange(ConfigChangeEvent changeEvent) {
            log.info("Detected a change in the configuration from Apollo, refreshing configurations, changed namespace : {} keys: {}.",
                    changeEvent.getNamespace(), changeEvent.changedKeys());
            // 查询元数据。热更新的频率较低，为了避免出现bean出现动态增删的情况，每次配置更新的时候都需要重新在应用上下文中查出
            ArrayList<PropertiesMetadata> mtds = new ArrayList<>();
            Map<String, Object> beans = applicationContext.getBeansWithAnnotation(ConfigurationProperties.class);
            for (Map.Entry<String, Object> entry : beans.entrySet()) {
                String beanName = entry.getKey();
                Object bean = entry.getValue();
                ConfigurationProperties configurationProperties
                        = applicationContext.findAnnotationOnBean(beanName, ConfigurationProperties.class);
                LiveUpdateReminder liveUpdateReminder
                        = applicationContext.findAnnotationOnBean(beanName, LiveUpdateReminder.class);
                mtds.add(new PropertiesMetadata(beanName, bean, configurationProperties, liveUpdateReminder));
            }

            for (String changedKey : changeEvent.changedKeys()) {
                for (PropertiesMetadata metadata : mtds) {
                    // 重新绑定值
                    if (!changedKey.startsWith(metadata.getConfigurationProperties().prefix())) {
                        continue;
                    }
                    log.info("{} changed, rebind and remind.", metadata.getBeanName());
                    propertiesRebinder.rebind(metadata.getBeanName());
                    // 如果有标记更新之后需要发布事件，则发布事件
                    if (metadata.getLiveUpdateReminder() == null) {
                        continue;
                    }
                    for (Class<? extends LiveUpdateEvent> event : metadata.getLiveUpdateReminder().events()) {
                        publisher.publishEvent(BeanUtils.instantiateClass(event));
                    }
                }
            }
        }
    }
}
